{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators     #-}

module Cardano.Wallet.API.V1.Handlers.Transactions (
      handlers
    , newTransaction
    , getTransactionsHistory
    , estimateFees
    -- | Helper converter.
    , txFromMeta
    ) where

import           Universum

import           Servant

import           Data.Coerce (coerce)

import           Pos.Chain.Txp (TxId)
import           Pos.Client.Txp.Util (defaultInputSelectionPolicy)
import           Pos.Core (Address, Timestamp)

import           Cardano.Wallet.API.Request
import           Cardano.Wallet.API.Response
import qualified Cardano.Wallet.API.V1.Transactions as Transactions
import           Cardano.Wallet.API.V1.Types
import           Cardano.Wallet.Kernel.CoinSelection.FromGeneric
                     (ExpenseRegulation (..))
import           Cardano.Wallet.Kernel.DB.HdWallet (UnknownHdAccount)
import           Cardano.Wallet.Kernel.DB.TxMeta (TxMeta)
import qualified Cardano.Wallet.Kernel.Transactions as Kernel
import           Cardano.Wallet.WalletLayer (ActiveWalletLayer,
                     NewPaymentError (..), PassiveWalletLayer)
import qualified Cardano.Wallet.WalletLayer as WalletLayer
import           Cardano.Wallet.WalletLayer.Kernel.Conv (toInputGrouping)

handlers :: ActiveWalletLayer IO -> ServerT Transactions.API Handler
handlers aw = newTransaction aw
         :<|> getTransactionsHistory (WalletLayer.walletPassiveLayer aw)
         :<|> estimateFees aw
         :<|> redeemAda aw

-- | Given a 'Payment' as input, tries to generate a new 'Transaction', submitting
-- it to the network eventually.
newTransaction :: ActiveWalletLayer IO
               -> Payment
               -> Handler (APIResponse Transaction)
newTransaction aw payment@Payment{..} = liftIO $ do

    -- NOTE(adn) The 'SenderPaysFee' option will become configurable as part
    -- of CBR-291.
    let inputGrouping = toInputGrouping $ fromMaybe (V1 defaultInputSelectionPolicy)
                                                    pmtGroupingPolicy
    res <- liftIO $ (WalletLayer.pay aw) (maybe mempty coerce pmtSpendingPassword)
                                         inputGrouping
                                         SenderPaysFee
                                         payment
    case res of
         Left err        -> throwM err
         Right (_, meta) -> txFromMeta aw NewPaymentUnknownAccountId meta

txFromMeta :: Exception e
           => ActiveWalletLayer IO
           -> (UnknownHdAccount -> e)
           -> TxMeta
           -> IO (APIResponse Transaction)
txFromMeta aw embedErr meta = do
    mTx <- WalletLayer.getTxFromMeta (WalletLayer.walletPassiveLayer aw) meta
    case mTx of
      Left err -> throwM (embedErr err)
      Right tx -> return $ single tx

getTransactionsHistory :: PassiveWalletLayer IO
                       -> Maybe WalletId
                       -> Maybe AccountIndex
                       -> Maybe (V1 Address)
                       -> RequestParams
                       -> FilterOperations '[V1 TxId, V1 Timestamp] Transaction
                       -> SortOperations Transaction
                       -> Handler (APIResponse [Transaction])
getTransactionsHistory pw mwalletId mAccIdx mAddr requestParams fops sops =
    liftIO $ do
        mRes <- WalletLayer.getTransactions pw mwalletId mAccIdx mAddr requestParams fops sops
        case mRes of
            Left err  -> throwM err
            Right res -> return res

-- | Computes the fees generated by this payment, without actually sending
-- the transaction to the network.
estimateFees :: ActiveWalletLayer IO
             -> Payment
             -> Handler (APIResponse EstimatedFees)
estimateFees aw payment@Payment{..} = do
    let inputGrouping = toInputGrouping $ fromMaybe (V1 defaultInputSelectionPolicy)
                                                    pmtGroupingPolicy
    res <- liftIO $ (WalletLayer.estimateFees aw) inputGrouping
                                                  SenderPaysFee
                                                  payment
    case res of
         Left err  -> throwM err
         Right fee -> return $ single (EstimatedFees (V1 fee))

redeemAda :: ActiveWalletLayer IO
          -> Redemption
          -> Handler (APIResponse Transaction)
redeemAda aw redemption = liftIO $ do
    res <- WalletLayer.redeemAda aw redemption
    case res of
      Left err        -> throwM err
      Right (_, meta) -> txFromMeta aw embedErr meta
  where
    embedErr :: UnknownHdAccount -> WalletLayer.RedeemAdaError
    embedErr = WalletLayer.RedeemAdaError . Kernel.RedeemAdaUnknownAccountId
